﻿#region COPYRIGHT© 2005-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;

namespace FlitBit.Parse
{
	/// <summary>
	/// A tokenizer's state.
	/// </summary>
	/// <typeparam name="TKind"></typeparam>
	public class TokenizerState<TKind> : IEnumerator<Token<TKind>>
		where TKind : struct
	{
		Token<TKind> _current;
		readonly IEnumerator<Token<TKind>> _inner;
		Queue<Token<TKind>> _lookahead = new Queue<Token<TKind>>();
		readonly Stack<object> _scope = new Stack<object>();

		/// <summary>
		/// Creates a new instance.
		/// </summary>
		/// <param name="tokens"></param>
		public TokenizerState(IEnumerable<Token<TKind>> tokens)
		{
			Contract.Requires(tokens != null, "tokens cannot be null");
			_inner = tokens.GetEnumerator();
		}

		/// <summary>
		///   A handback object; typically used by tokenizers.
		/// </summary>
		public object Handback { get; internal set; }

		/// <summary>
		/// The current scope.
		/// </summary>
		public Stack<Token<TKind>> Scope { get; private set; }
		/// <summary>
		/// Returns the scope depth;
		/// </summary>
		public int ScopeDepth { get { return _scope.Count; } }

		/// <summary>
		///   Enqueues tokens on the lookahead queue.
		/// </summary>
		/// <param name="token">the token</param>
		public void EnqueueLookahead(Token<TKind> token)
		{
			_lookahead.Enqueue(token);
		}

		/// <summary>
		/// Ensures the next token is of the kind expected.
		/// </summary>
		/// <param name="kind"></param>
		/// <returns></returns>
		/// <exception cref="ParseException">thrown if the next token is not of the expected kind.</exception>
		public Token<TKind> ExpectAt(TKind kind)
		{
			var current = _current;
			if (MoveNext())
			{
				var next = _current;
				if (EqualityComparer<TKind>.Default.Equals(next.Kind, kind))
				{
					InsertLookahead(next);
					return next;
				}
				if (EqualityComparer<TKind>.Default.Equals(next.Kind, Tokenizer<TKind>.ErrorKind))
				{
					throw new ParseException(next.Capture.Description);
				}
				throw new ParseException(MakeMessageForFailedExpectation(next.Capture.Position, next.Kind, kind));
			}
			throw new ParseException(MakeMessageForFailedExpectation("Unexpected end of input",
																															current.Capture.Position + current.Capture.Length, false, Tokenizer<TKind>.ErrorKind,
																															kind));
		}

		/// <summary>
		/// Ensures the next token is one of the kinds expected.
		/// </summary>
		/// <param name="kinds"></param>
		/// <returns></returns>
		/// <exception cref="ParseException">thrown if the next token is not one of the expected kinds.</exception>
		public Token<TKind> ExpectAtAny(params TKind[] kinds)
		{
			var current = _current;
			if (MoveNext())
			{
				var next = _current;
				if (next.Kind.IsOneOf(kinds))
				{
					InsertLookahead(next);
					return next;
				}
				if (EqualityComparer<TKind>.Default.Equals(next.Kind, Tokenizer<TKind>.ErrorKind))
				{
					throw new ParseException(next.Capture.Description);
				}
				throw new ParseException(MakeMessageForFailedExpectation(next.Capture.Position, next.Kind, kinds));
			}
			throw new ParseException(MakeMessageForFailedExpectation("Unexpected end of input",
																															current.Capture.Position + current.Capture.Length, false, Tokenizer<TKind>.ErrorKind,
																															kinds));
		}

		/// <summary>
		/// Ensures the next token is one of the expected kinds or EOF.
		/// </summary>
		/// <param name="values"></param>
		/// <returns></returns>
		/// <exception cref="ParseException">thrown if the next token is not one of the expected kinds or EOF.</exception>
		public Token<TKind> ExpectAtAnyOrEof(params TKind[] values)
		{
			var current = _current;
			if (MoveNext())
			{
				var next = _current;
				if (next.Kind.IsOneOf(values))
				{
					InsertLookahead(next);
					return next;
				}
				if (EqualityComparer<TKind>.Default.Equals(next.Kind, Tokenizer<TKind>.EofKind))
				{
					throw new ParseException(next.Capture.Description);
				}
				throw new ParseException(MakeMessageForFailedExpectation(next.Capture.Position, next.Kind, values));
			}
			return new Token<TKind>(TokenSpec.EofSpec, Tokenizer<TKind>.EofKind, new Capture(Match.Eof,
																																											current.Capture.Position.Increment(current.Capture.Length), String.Empty));
		}

		/// <summary>
		/// Ensures the next token is of the expected kind and advances the enumeration.
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public Token<TKind> ExpectTake(TKind value)
		{
			var current = _current;
			if (MoveNext())
			{
				var next = _current;
				if (EqualityComparer<TKind>.Default.Equals(next.Kind, value))
				{
					return next;
				}
				if (EqualityComparer<TKind>.Default.Equals(next.Kind, Tokenizer<TKind>.ErrorKind))
				{
					throw new ParseException(next.Capture.Description);
				}
				throw new ParseException(MakeMessageForFailedExpectation(next.Capture.Position, next.Kind, value));
			}
			throw new ParseException(MakeMessageForFailedExpectation("Unexpected end of input",
																															current.Capture.Position + current.Capture.Length, false, Tokenizer<TKind>.ErrorKind,
																															value));
		}

		/// <summary>
		/// Ensures the next token is one of the kinds expected.
		/// </summary>
		/// <param name="kinds"></param>
		/// <returns></returns>
		/// <exception cref="ParseException">thrown if the next token is not one of the expected kinds.</exception>
		public Token<TKind> ExpectTakeAny(params TKind[] kinds)
		{
			var current = _current;
			if (MoveNext())
			{
				if (_current.Kind.IsOneOf(kinds))
				{
					return _current;
				}
				if (EqualityComparer<TKind>.Default.Equals(this._current.Kind, Tokenizer<TKind>.ErrorKind))
				{
					throw new ParseException(this._current.Capture.Description);
				}
				throw new ParseException(MakeMessageForFailedExpectation(this._current.Capture.Position, this._current.Kind, kinds));
			}
			throw new ParseException(MakeMessageForFailedExpectation("Unexpected end of input",
																															current.Capture.Position + current.Capture.Length, false, Tokenizer<TKind>.ErrorKind,
																															kinds));
		}

		/// <summary>
		///   Pushes token onto the front of the lookahead queue
		/// </summary>
		/// <param name="token"></param>
		public void InsertLookahead(Token<TKind> token)
		{
			_lookahead = new Queue<Token<TKind>>(new[] {token}.Concat(_lookahead));
		}

		/// <summary>
		/// Pushes tokens onto the front of the lookahead queue.
		/// </summary>
		/// <param name="lookahead"></param>
		public void InsertLookahead(IEnumerable<Token<TKind>> lookahead)
		{
			_lookahead = new Queue<Token<TKind>>(lookahead.Concat(_lookahead));
		}

		/// <summary>
		/// Ensures the next token is one of the expected token kinds.
		/// </summary>
		/// <param name="values"></param>
		/// <returns></returns>
		public Token<TKind> MustBeAtEndOrFollowedByAny(params TKind[] values)
		{
			var current = _current;
			try
			{
				if (!MoveNext())
				{
					return default(Token<TKind>);
				}
				else
				{
					var next = this._current;
					if (next.IsEndOfInput || next.Kind.IsOneOf(values))
					{
						EnqueueLookahead(next);
						return next;
					}
					else
					{
						var err = new Token<TKind>(TokenSpec.ErrorSpec, Tokenizer<TKind>.ErrorKind,
																			new Capture(Match.NullMatch,
																									next.Capture.Position,
																									next.Capture.Value,
																									MakeMessageForFailedExpectation(next.Capture.Position, next.Kind, values))
							);
						EnqueueLookahead(err);
						return err;
					}
				}
			}
			finally
			{
				_current = current;
			}
		}

		/// <summary>
		/// Ensures the next token is the expected kind.
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		public Token<TKind> MustBeFollowedBy(TKind value)
		{
			var current = _current;
			try
			{
				if (MoveNext())
				{
					var next = _current;
					if (EqualityComparer<TKind>.Default.Equals(next.Kind, value))
					{
						EnqueueLookahead(next);
						return next;
					}
					else
					{
						var err = new Token<TKind>(TokenSpec.ErrorSpec, Tokenizer<TKind>.ErrorKind,
																			new Capture(Match.NullMatch,
																									next.Capture.Position,
																									next.Capture.Value,
																									MakeMessageForFailedExpectation(next.Capture.Position, next.Kind, value))
							);
						EnqueueLookahead(err);
						return err;
					}
				}
				else
				{
					var err = new Token<TKind>(TokenSpec.ErrorSpec, Tokenizer<TKind>.ErrorKind,
																		new Capture(Match.NullMatch,
																								current.Capture.Position + current.Capture.Length,
																								"EOF", "Unexpected end of input"));
					EnqueueLookahead(err);
					return err;
				}
			}
			finally
			{
				_current = current;
			}
		}

		/// <summary>
		/// Ensures the next token is one of the expected kinds.
		/// </summary>
		/// <param name="values"></param>
		/// <returns></returns>
		public Token<TKind> MustBeFollowedByAny(params TKind[] values)
		{
			var current = _current;
			try
			{
				if (MoveNext())
				{
					var next = _current;
					if (next.Kind.IsOneOf(values))
					{
						EnqueueLookahead(next);
						return next;
					}
					else
					{
						var err = new Token<TKind>(TokenSpec.ErrorSpec, Tokenizer<TKind>.ErrorKind,
																			new Capture(Match.NullMatch,
																									next.Capture.Position,
																									next.Capture.Value,
																									MakeMessageForFailedExpectation(next.Capture.Position, next.Kind, values))
							);
						EnqueueLookahead(err);
						return err;
					}
				}
				else
				{
					var err = new Token<TKind>(TokenSpec.ErrorSpec, Tokenizer<TKind>.ErrorKind,
																		new Capture(Match.NullMatch,
																								current.Capture.Position + current.Capture.Length,
																								"EOF", "Unexpected end of input"));
					EnqueueLookahead(err);
					return err;
				}
			}
			finally
			{
				_current = current;
			}
		}

		/// <summary>
		/// Peeks at the scope.
		/// </summary>
		/// <returns></returns>
		public object PeekScope()
		{
			return _scope.Peek();
		}

		/// <summary>
		/// Peeks at the scope.
		/// </summary>
		/// <typeparam name="S"></typeparam>
		/// <returns></returns>
		public S PeekScope<S>()
		{
			return (S) _scope.Peek();
		}

		/// <summary>
		/// Pops the scope.
		/// </summary>
		/// <returns></returns>
		public object PopScope()
		{
			return _scope.Pop();
		}

		/// <summary>
		/// Pops the scope.
		/// </summary>
		/// <typeparam name="S"></typeparam>
		/// <returns></returns>
		public S PopScope<S>()
		{
			return (S) _scope.Pop();
		}

		/// <summary>
		/// Pushes a scope.
		/// </summary>
		/// <param name="value"></param>
		public void PushScope(object value)
		{
			_scope.Push(value);
		}

		/// <summary>
		/// Skips any tokens of the kinds given.
		/// </summary>
		/// <param name="values"></param>
		/// <returns></returns>
		public IEnumerable<Token<TKind>> SkipAny(params TKind[] values)
		{
			var skipped = new List<Token<TKind>>();
			while (MoveNext())
			{
				var next = _current;
				if (next.Kind.IsOneOf(values))
				{
					skipped.Add(next);
				}
				else
				{
					InsertLookahead(next);
					break;
				}
			}
			return skipped;
		}

		/// <summary>
		/// Takes any tokens until one of the kinds given are encountered.
		/// </summary>
		/// <param name="kinds"></param>
		/// <returns></returns>
		public IEnumerable<Token<TKind>> TakeUntilAny(params TKind[] kinds)
		{
			var taken = new List<Token<TKind>>();
			while (MoveNext())
			{
				var next = _current;
				if (!next.Kind.IsOneOf(kinds))
				{
					taken.Add(next);
				}
				else
				{
					InsertLookahead(next);
					break;
				}
			}
			return taken;
		}

		#region IEnumerator<Token<TKind>> Members

		/// <summary>
		/// Gets the current token.
		/// </summary>
		public Token<TKind> Current { get { return _current; } }

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		/// <filterpriority>2</filterpriority>
		public void Dispose()
		{
			if (_inner != null)
			{
				_inner.Dispose();
			}
		}

		object IEnumerator.Current { get { return _current; } }

		/// <summary>
		/// Advances the enumerator to the next element of the collection.
		/// </summary>
		/// <returns>
		/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
		/// </returns>
		/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
		public bool MoveNext()
		{
			var result = false;
			if (_lookahead.Count > 0)
			{
				_current = _lookahead.Dequeue();
				result = true;
			}
			else if (_inner.MoveNext())
			{
				_current = _inner.Current;
				result = true;
			}
			return result;
		}

		/// <summary>
		/// Sets the enumerator to its initial position, which is before the first element in the collection.
		/// </summary>
		/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
		public void Reset()
		{
			_inner.Reset();
			_current = default(Token<TKind>);
		}

		#endregion

		/// <summary>
		/// Creates a readable error message about a failed expectation.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="message"></param>
		/// <param name="curs"></param>
		/// <param name="useFound"></param>
		/// <param name="found"></param>
		/// <param name="expected"></param>
		/// <returns></returns>
		public static string MakeMessageForFailedExpectation<T>(string message, Cursor curs, bool useFound, T found,
			params T[] expected)
		{
			var buffer = new StringBuilder(400)
				.Append(message)
				.Append(" at position: ")
				.Append(curs)
				.Append(". Expected: ");
			for (var i = 0; i < expected.Length; ++i)
			{
				if (i > 0)
				{
					buffer.Append(i == expected.Length - 1 ? " or " : ", ");
				}
				buffer.Append(Convert.ToString(expected[i]));
			}
			buffer.Append(".");
			if (useFound)
			{
				buffer.Append(" Found: ")
							.Append(Convert.ToString(found));
			}
			return buffer.ToString();
		}

		/// <summary>
		/// Creates a readable error message about a failed expectation.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="curs"></param>
		/// <param name="found"></param>
		/// <param name="expected"></param>
		/// <returns></returns>
		public static string MakeMessageForFailedExpectation<T>(Cursor curs, T found, params T[] expected)
		{
			return MakeMessageForFailedExpectation("Error", curs, true, found, expected);
		}
	}
}